<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link href="../css/main.css" media="all" rel="stylesheet" type="text/css" />
<link href="../css/highlight.css" media="all" rel="stylesheet" type="text/css" />
<link href="../css/print.css" media="print" rel="stylesheet" type="text/css" />
<title>第8章 - モデルレイヤーの内側</title>
</head>

<body>
<div class="navigation">

<table width="100%">
<tr>
<td width="40%" align="left"><a href="07-Inside-the-View-Layer.html">前の章</a></td>
<td width="20%" align="center"><a href="index.html">ホーム</a></td>
<td width="40%" align="right"><a href="09-Links-and-the-Routing-System.html">次の章</a></td>
</tr>
</table>
<hr/>
</div>

<div>
<a name="chapter.8.inside.the.model.layer" id="chapter.8.inside.the.model.layer"></a><h1>第8章 - モデルレイヤーの内側</h1>

<p>これまでのところ、ページを作り、リクエストとレスポンスを処理することに多くの検討が行われてきました。しかしながらWebアプリケーションのビジネスロジックの多くはデータモデルに依存しています。symfonyのデフォルトモデルのコンポーネントはPropelのプロジェクト(<a href="http://propel.phpdb.org/trac/">http://propel.phpdb.org/trac/</a>)で知られるオブジェクト/リレーショナルマッピング(ORM - Object/Relational Mapping)のレイヤーに基づいています。symfonyのアプリケーションにおいて、アプリケーションの開発者はオブジェクトを通してデータベースに保存されたデータにアクセスし、オブジェクトを通してこれを修正します。決して明確にデータベースにとり組みません。このことによって高い抽象性と移植性が維持されます。</p>

<p>この章では、オブジェクトのデータモデルを作成する方法と、Propelのデータにアクセスして修正する方法を説明します。これはPropelがsymfonyに統合されていることも実証します。</p>

<div class="toc">
<dl>
<dt><a href="#why.use.an.orm.and.an.abstraction.layer">8.1. なぜORMと抽象化レイヤーを使うのか？</a></dt>
<dt><a href="#symfonys.database.schema">8.2. symfonyのデータベーススキーマ</a></dt>
<dd><dl>
<dt><a href="#schema.example">8.2.1. スキーマの例</a></dt>
<dt><a href="#basic.schema.syntax">8.2.2. 基本的なスキーマ構文</a></dt>
</dl></dd>
<dt><a href="#model.classes">8.3. モデルクラス</a></dt>
<dd><dl>
<dt><a href="#base.and.custom.classes">8.3.1. 基底とカスタムクラス</a></dt>
<dt><a href="#object.and.peer.classes">8.3.2. オブジェクトクラスとピアクラス</a></dt>
</dl></dd>
<dt><a href="#accessing.data">8.4. データにアクセスする</a></dt>
<dd><dl>
<dt><a href="#retrieving.the.column.value">8.4.1. カラムの値を検索する</a></dt>
<dt><a href="#retrieving.related.records">8.4.2. 関連するレコードを検索する</a></dt>
<dt><a href="#saving.and.deleting.data">8.4.3. データの保存と削除を行う</a></dt>
<dt><a href="#retrieving.records.by.primary.key">8.4.4. 主キーでレコードをとり出す</a></dt>
<dt><a href="#retrieving.records.with.criteria">8.4.5. Criteriaでレコードを検索する</a></dt>
<dt><a href="#using.raw.sql.queries">8.4.6. 生のSQLクエリを使う</a></dt>
<dt><a href="#using.special.date.columns">8.4.7. 特別な日付カラムを使う</a></dt>
</dl></dd>
<dt><a href="#database.connections">8.5. データベースの接続</a></dt>
<dt><a href="#extending.the.model">8.6. モデルを拡張する</a></dt>
<dd><dl>
<dt><a href="#adding.new.methods">8.6.1. 新しいメソッドを追加する</a></dt>
<dt><a href="#overriding.existing.methods">8.6.2. 既存のメソッドをオーバーライドする</a></dt>
<dt><a href="#using.model.behaviors">8.6.3. モデルのビヘイビアーを使う</a></dt>
</dl></dd>
<dt><a href="#extended.schema.syntax">8.7. スキーマの拡張構文</a></dt>
<dd><dl>
<dt><a href="#attributes">8.7.1. 属性</a></dt>
<dt><a href="#column.details">8.7.2. カラムの詳細</a></dt>
<dt><a href="#foreign.keys">8.7.3. 外部キー</a></dt>
<dt><a href="#indexes">8.7.4. インデックス</a></dt>
<dt><a href="#empty.columns">8.7.5. 空のカラム</a></dt>
<dt><a href="#i18n.tables">8.7.6. 国際化テーブル</a></dt>
<dt><a href="#behaviors.new.in.symfony.1.1">8.7.7. ビヘイビアー (symfony 1.1の新しい機能)</a></dt>
<dt><a href="#beyond.the.schema.yml.the.schema.xml">8.7.8. schema.ymlを越えて: schema.xml</a></dt>
</dl></dd>
<dt><a href="#dont.create.the.model.twice">8.8. 同じモデルを2回作らない</a></dt>
<dd><dl>
<dt><a href="#building.a.sql.database.structure.based.on.an.existing.schema">8.8.1. 既存のスキーマに基づいてSQLのデータベース構造をビルドする</a></dt>
<dt><a href="#generating.a.yaml.data.model.from.an.existing.database">8.8.2. 既存のデータベースからYAMLのデータモデルを生成する</a></dt>
</dl></dd>
<dt><a href="#summary">8.9. まとめ</a></dt>
</dl>
</div>
<a name="why.use.an.orm.and.an.abstraction.layer" id="why.use.an.orm.and.an.abstraction.layer"></a><h2>なぜORMと抽象化レイヤーを使うのか？</h2>

<p>データベースはリレーショナルです。一方でPHP 5とsymfonyはオブジェクト指向です。オブジェクト指向のコンテキストでもっとも効果的にデータベースにアクセスするには、オブジェクトをリレーショナルなロジックに変換するインターフェイスが求められます。1章で説明されたように、このインターフェイスはオブジェクトリレーショナルマッピング(ORM - Object-Relational Mapping)と呼ばれ、データにアクセスしてオブジェクトの範囲でビジネスのルールを維持するオブジェクトで構成されます。</p>

<p>ORMを使う主な利点は再利用性です。アプリケーションのさまざまな部分から、異なるアプリケーションからでも、データオブジェクトのメソッドを呼び出すことができます。ORMレイヤーはデータロジックもカプセル化します。たとえば、行われた投稿回数とそれらの投稿がどれだけ人気なのかに基づいてフォーラムのユーザーの評価を計算する方法です。ページがそのようなユーザーの評価を表示する必要があるとき、詳細な計算に悩むことなくsymfonyはデータモデルのメソッドを簡単に呼び出します。計算方法があとで変わった場合、必要なことはモデルの評価メソッドを修正することだけで、アプリケーションの残りの部分はそのままにできます。</p>

<p>レコードの代わりにオブジェクトを使い、テーブルの代わりにクラスを使うことには別の利点があります: これらによって新しいアクセサーをテーブルのカラムにかならずしもマッチしないオブジェクトに追加できます。<code>client</code>という名前のテーブルが存在し、これが<code>first_name</code>と<code>last_name</code>という2つのフィールドを持つ場合、<code>Name</code>だけを求めたい場合を考えます。オブジェクト指向の世界において、リスト8-1のように、新しいアクセサーメソッドを<code>Client</code>クラスに追加することと同じぐらい簡単です。アプリケーションの観点から、<code>Client</code>クラスの<code>FirstName</code>、<code>LastName</code>、と<code>Name</code>属性の間の違いは存在しません。クラス自身がどの属性がデータベースのカラムに対応するのかを決定できます。</p>

<p>リスト8-1 - アクセサーはモデルクラスの実際のテーブル構造を覆い隠す</p>

<pre class="php"><span class="kw2">public</span> <span class="kw2">function</span> getName<span class="br0">&#40;</span><span class="br0">&#41;</span>
<span class="br0">&#123;</span>
  <span class="kw1">return</span> <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">getFirstName</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">.</span><span class="st_h">' '</span><span class="sy0">.</span><span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">getLastName</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span>
<span class="br0">&#125;</span></pre>

<p>すべての繰り返されるデータアクセス関数とデータのビジネスロジック自身はこのようなオブジェクトのなかに保たれます。<code>Items</code>(オブジェクト)を持つ<code>ShoppingCart</code>クラスを考えてみましょう。精算のためにショッピングカートの全額を得る方法は、リスト8-2で示されるように、実際の計算をカプセル化するカスタムメソッドを書くことです。</p>

<p>リスト8-2 - アクセサーはデータロジックを覆い隠す</p>

<pre class="php"><span class="kw2">public</span> <span class="kw2">function</span> getTotal<span class="br0">&#40;</span><span class="br0">&#41;</span>
<span class="br0">&#123;</span>
  <span class="re0">$total</span> <span class="sy0">=</span> <span class="nu0">0</span><span class="sy0">;</span>
  <span class="kw1">foreach</span> <span class="br0">&#40;</span><span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">getItems</span><span class="br0">&#40;</span><span class="br0">&#41;</span> <span class="kw1">as</span> <span class="re0">$item</span><span class="br0">&#41;</span>
  <span class="br0">&#123;</span>
    <span class="re0">$total</span> <span class="sy0">+=</span> <span class="re0">$item</span><span class="sy0">-&gt;</span><span class="me1">getPrice</span><span class="br0">&#40;</span><span class="br0">&#41;</span> <span class="sy0">*</span> <span class="re0">$item</span><span class="sy0">-&gt;</span><span class="me1">getQuantity</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span>
  <span class="br0">&#125;</span>
&nbsp;
  <span class="kw1">return</span> <span class="re0">$total</span><span class="sy0">;</span>
<span class="br0">&#125;</span></pre>

<p>データとアクセスの手順を設けるときに考慮すべき別の重要な点があります: データベースベンダーは異なるSQL構文の方言を使います。ほかのデータベースマネジメントシステム(DBMS)に切り替えると以前のDBMSのために設計されたSQLクエリの部分を書き直さなければなりません。データベースから独立した構文を使うクエリを作り、サードパーティのコンポーネントに実際のSQLの翻訳を任せておけば、苦痛をともなわずにデータベースの構文を切り替えることができます。これがデータベースの抽象化レイヤーの目的です。これによってクエリに対して特定の構文を使うことが強制され、DBMSの固有機能に適合してSQLコードを最適化する汚い作業が推進されます。</p>

<p>抽象化レイヤーの主な利点は、移植性です。これによって、プロジェクトの真っ最中でも、別のデータベースに切り替えることができます。アプリケーションに対して迅速にプロトタイプを書く必要があるが、顧客が自身のニーズに最適なデータベースシステムがどれなのかを決断していない場合を考えてみましょう。SQLiteでアプリケーションの開発を始めることが可能であり、たとえば、顧客が決断をする準備ができたときに、、MySQL、PostgreSQL、またはOracleに切り替えます。設定ファイルの一行を変更すれば、アプリケーションは動きます。</p>

<p>symfonyはPropelをORMとして利用し、Propelはデータベースの抽象化のためにCreoleを利用します。これら2つのサードパーティのコンポーネントは、両方ともPropelの開発チームによって開発され、symfonyにシームレスに統合されているので、これらをフレームワークの一部としてみなすことができます。この章で説明しますが、これらの構文と規約はできるかぎりsymfonyのものとは異ならないように採用されました。</p>

<blockquote class="note"><p>
  symfonyのプロジェクトにおいて、すべてのアプリケーションは同じモデルを共有します。これがプロジェクトレベルの肝心な点: 共通のビジネスルールに依存するアプリケーションを再編することです。モデルがアプリケーションから独立しており、モデルのファイルがプロジェクトのrootの<code>lib/model/</code>ディレクトリに保存される理由です。</p>
</blockquote>

<a name="symfonys.database.schema" id="symfonys.database.schema"></a><h2>symfonyのデータベーススキーマ</h2>

<p>symfonyが使うデータオブジェクトモデルを作成するために、データベースが持つリレーショナルモデルはどんなものでもオブジェクトデータモデルに翻訳する必要があります。ORMはマッピングを行うためにリレーショナルモデルの記述が必要です。これを記述するものはスキーマ(schema)と呼ばれます。スキーマにおいて、開発者はテーブル、それらのリレーション、とカラムの特徴を定義します。</p>

<p>スキーマのためのsymfonyの構文はYAMLフォーマットを利用します。<code>schema.yml</code>ファイルは<code>myproject/config/</code>ディレクトリ内部に設置しなければなりません。</p>

<blockquote class="note"><p>
  symfonyはこの章の後のほうにある"schema.ymlを越えて: schema.xml"のセッションで説明されるPropelのネイティブなXML形式のスキーマも理解します。</p>
</blockquote>

<a name="schema.example" id="schema.example"></a><h3>スキーマの例</h3>

<p>データベースの構造をスキーマにどのように変換しますか？具体例は理解するための最良の方法です。2つのテーブル: <code>blog_article</code>と<code>blog_comment</code>を持つblogのデータベースを想像してください。テーブルの構造は図8-1で示されています。</p>

<p>図8-1 - blogのデータベースのテーブル構造</p>

<p><img src="images/F0801.png" alt="blogのデータベースのテーブル構造" title="blogのデータベースのテーブル構造" /></p>

<p>関連する<code>schema.yml</code>ファイルはリスト8-3のようになります。</p>

<p>リスト8-3 - <code>schema.yml</code>のサンプル</p>

<pre class="yml">propel:
  blog_article:
    _attributes: { phpName: Article }
    id:
    title:       varchar(255)
    content:     longvarchar
    created_at:
  blog_comment:
    _attributes: { phpName: Comment }
    id:
    article_id:
    author:      varchar(255)
    content:     longvarchar
    created_at:</pre>

<p>データベース自身(<code>blog</code>)の名前は<code>schema.yml</code>には登場しないことに注目してください。代わりに、データベースの内容は接続名(この例では<code>propel</code>)の下に記述されます。これは実際の接続設定はアプリケーションが稼働している環境に依存する可能性があるからです。たとえば、開発環境においてアプリケーションを稼働させるとき、開発データベース(たとえば<code>blog_dev</code>)にアクセスすることになりますが、運用のデータベースも同じスキーマを使います。接続設定は<code>databases.yml</code>ファイルのなかで指定されます。このファイルはこの章の後のほうの"データベースの接続"のセクションで説明します。スキーマは、データベースの抽象化を保つために、詳細な接続情報を設定に含まず、接続名だけを含みます。</p>

<a name="basic.schema.syntax" id="basic.schema.syntax"></a><h3>基本的なスキーマ構文</h3>

<p><code>schema.yml</code>ファイルにおいて、最初のキーは接続名を表します。これは、テーブルをいくつか含むことができます。それぞれのテーブルはカラムのセットを持ちます。YAMLの構文に従い、キーはコロンで終わり、構造はインデント(1つか複数のスペース、ただしタブはなし)を通して示されます。</p>

<p>テーブルは<code>phpName</code>(生成されるクラスの名前)を含めて、特別な属性を持つことができます。<code>phpName</code>がテーブルに記載されていない場合、symfonyはcamelCase(キャメルケース)バージョンの名前でそのテーブルを作ります。</p>

<blockquote class="tip"><p>
  camelCaseの規約によれば単語からアンダースコアをとり除き、内部の単語の最初の文字を大文字にします。<code>blog_article</code>と<code>blog_comment</code>のデフォルトのcamelCaseバージョンは<code>BlogArticle</code>と<code>BlogComment</code>です。この規約名は長い単語内部の大文字がラクダのコブに見えることから由来しています。</p>
</blockquote>

<p>テーブルはカラムを含みます。カラムの値は3つの異なった方法で定義できます:</p>

<ul>
<li>何も定義していない場合、symfonyはカラムの名前といくつかの規約にしたがってベストな属性を推測します。カラムの名前と規約はこの章の後のほうにある"空のカラム"のセクションで説明されます。たとえば、リスト8-3にある<code>id</code>カラムは定義する必要はありません。symfonyはそれを、オートインクリメントの整数型で、テーブルの主キーと定義します。<code>blog_comment</code>テーブルの<code>article_id</code>は<code>blog_article</code>テーブルへの外部キーとして理解されます(<code>_id</code>で終わるカラムは外部キーとして見なされ、関連するテーブルはカラム名の最初の部分にしたがって自動的に決定されます)。<code>created_at</code>という名前のカラムは自動的に<code>timestamp</code>型に設定されます。これらすべてのカラムに対して、型を指定する必要はありません。それが<code>schema.yml</code>を書くことがなぜ簡単であるかの理由の1つです。 </li>
<li>1つの属性だけを定義する場合、これはカラム型です。symfonyは通常のカラムの型を理解します: <code>boolean</code>、<code>integer</code>、<code>float</code>、<code>date</code>、<code>varchar(size)</code>、 <code>longvarchar</code>(たとえばMySQLでは<code>text</code>に変換されます)などです。256文字を越えたテキストの内容に対しては、サイズを持たない<code>longvarchar</code>型(MySQLでは65KBを越えることはできません)を使う必要があります。<code>date</code>と<code>timestamp</code>型は通常のUnixの日付の制限を持ち、1970年1月1日以前の日付を設定することはできません。古い日付(たとえば誕生日)を設定する必要がある場合、(Unix起源の以前の)日付のフォーマットは<code>bu_date</code>と<code>bu_timestamp</code>で利用できます。</li>
<li>ほかのカラム属性を定義する必要がある場合(デフォルト値、求められた場合、など)、カラム属性を<code>key: value</code>のセットとして書きます。この拡張されたスキーマ構文はこの章の後のほうで説明します。</li>
</ul>

<p>カラムは大文字で始まるバージョンの名前(<code>Id</code>、<code>Title</code>、<code>Content</code>など)である、<code>phpName</code>属性を持ち、たいていの場合、オーバーライドする必要はありません。</p>

<p>テーブルは、わずかなデータベース固有の構造の定義と同様に、明示的な外部キーとインデックスを含むことができます。もっと学ぶにはこの章の後のほうにある"拡張されたスキーマ構文"のセクションを参照してください。</p>

<a name="model.classes" id="model.classes"></a><h2>モデルクラス</h2>

<p>スキーマはORMレイヤーのモデルクラスをビルドするために使われます。実行時間を節約するために、これらのクラスは<code>propel:build-model</code>という名前のコマンドラインタスクによって生成されます。</p>

<pre><code>&gt; php symfony propel:build-model
</code></pre>

<blockquote class="tip"><p>
  モデルをビルドしたあとで、symfonyが新しく生成されたモデルを見つけられるように、<code>php symfony cc</code>でsymfonyの内部キャッシュをクリアすることを覚えて置かなければなりません。</p>
</blockquote>

<p>このコマンドを入力することでスキーマの解析とプロジェクトの<code>lib/model/om/</code>ディレクトリの基底データモデルクラスの生成が実行されます:</p>

<ul>
<li><code>BaseArticle.php</code></li>
<li><code>BaseArticlePeer.php</code></li>
<li><code>BaseComment.php</code></li>
<li><code>BaseCommentPeer.php</code></li>
</ul>

<p>さらに、実際のデータモデルクラスは<code>lib/model/</code>のなかに作られます:</p>

<ul>
<li><code>Article.php</code></li>
<li><code>ArticlePeer.php</code></li>
<li><code>Comment.php</code></li>
<li><code>CommentPeer.php</code></li>
</ul>

<p>2つのテーブルだけを定義したので、8つのファイルで終わります。間違ったことは何もありませんが、いくつかの説明をする必要があります。</p>

<a name="base.and.custom.classes" id="base.and.custom.classes"></a><h3>基底とカスタムクラス</h3>

<p>2つのバージョンのデータオブジェクトを2つの異なったディレクトリに保存するのはなぜでしょうか？</p>

<p>おそらくカスタムメソッドとプロパティをモデルのオブジェクトに追加することが必要になります(リスト8-1の<code>getName()</code>メソッドを考えてください)。しかし、プロジェクトの開発に関しては、テーブルもしくはカラムも追加することになります。<code>schema.yml</code>ファイルを変更するたびに、<code>propel-build-model</code>を新しく呼び出してオブジェクトモデルクラスを再生成する必要があります。カスタムメソッドが実際に生成されたクラスのなかに書かれているとしたら、それらはそれぞれが生成された後に削除されます。</p>

<p><code>lib/model/om/</code>ディレクトリのなかに保存される<code>Base</code>クラスはスキーマから直接生成されたものです。これらを修正すべきではありません。すべての新しいモデルのビルドによっってこれらのファイルが完全に削除されるからです。</p>

<p>一方で、<code>lib/model/</code>ディレクトリのなかに保存される、カスタムオブジェクトクラスは実際には<code>Base</code>クラスから継承します。<code>propel:build-model</code>タスクが既存のモデルに呼び出されるとき、これらのクラスは修正されません。ですのでここがカスタムメソッドを追加できる場所です。</p>

<p>リスト8-4は<code>propel:build-model</code>タスクを最初に呼び出したときに作成されたカスタムモデルクラスの例を示しています。</p>

<p>リスト8-4 - モデルクラスのファイルのサンプル(<code>lib/model/Article.php</code>)</p>

<pre class="yml">class Article extends BaseArticle
{
}</pre>

<p>これは<code>BaseArticle</code>クラスのすべてのメソッドを継承しますが、スキーマ内の修正はこれに影響を与えません。</p>

<p>基底クラスを拡張するカスタムクラスのメカニズムによって、データベースの最終的なリレーショナルモデルを知らなくても、コードを書き始めることができます。関連ファイルの構造によってモデルはカスタマイズ可能で発展性のあるものになります。</p>

<a name="object.and.peer.classes" id="object.and.peer.classes"></a><h3>オブジェクトクラスとピアクラス</h3>

<p><code>Article</code>と<code>Comment</code>はデータベースのなかのレコードを表すオブジェクトクラスです。これらはレコードのカラムと関連するレコードにアクセスできます。リスト8-5で示される例のように、このことは<code>Article</code>オブジェクトのメソッドを呼び出すことで、記事のタイトルを知ることができることを意味します。</p>

<p>リスト8-5 - レコードカラムのためのゲッターはオブジェクトクラスで使える</p>

<pre class="yml">$article = new Article();
// ...
$title = $article-&gt;getTitle();</pre>

<p><code>ArticlePeer</code>と<code>CommentPeer</code>はピアクラスです; すなわち、テーブル上で実行する静的メソッドを含むクラスです。これらはテーブルからレコードを検索する方法を提供します。リスト8-6で示されるように、通常これらのメソッドはオブジェクトもしくは関連するオブジェクトクラスのオブジェクトの集まりを返します。</p>

<p>リスト8-6 -　レコードを検索する静的メソッドはピアクラスのなかで利用できる</p>

<pre class="yml">// $articlesはArticleクラスのオブジェクトの配列
$articles = ArticlePeer::retrieveByPks(array(123, 124, 125));</pre>

<blockquote class="note"><p>
  データモデルの観点から、複数のピアオブジェクトは存在できません。ピアクラスのメソッドが通常の<code>-&gt;</code>(インスタンスメソッドの呼び出し)の代わりに<code>::</code>(静的メソッドの呼び出し)で呼び出されるのはそういうわけです。</p>
</blockquote>

<p>基底とカスタムバージョンのオブジェクトクラスとピアクラスを結合した結果はスキーマのなかに記述されたテーブルごとに生成された4つのクラスになります。実際には、<code>lib/model/map/</code>ディレクトリのなかに生成された5番目のクラスが存在します。このディレクトリは実行環境のために必要なテーブルについてのメタデータ情報を含みます。しかしながら、おそらくこのクラスを変更することはないので、忘れてもかまいません。</p>

<a name="accessing.data" id="accessing.data"></a><h2>データにアクセスする</h2>

<p>symfonyにおいて、データはオブジェクトを通してアクセスされます。リレーショナルモデルとデータを検索し変更するSQLを使うことに慣れていたら、オブジェクトモデルのメソッドは複雑に見えるかもしれません。しかし、ひとたびデータアクセスのためのオブジェクト指向の力を味わえば、おそらくとても好きになるでしょう。</p>

<p>しかし最初は、同じ用語を共有していることを確認してみましょう。リレーショナルデータモデルとオブジェクトデータモデルは似たような概念を使いますが、これらはお互いに独自の命名法を持ちます:</p>

<table>
<thead>
<tr>
  <th>リレーショナル</th>
  <th>オブジェクト指向</th>
</tr>
</thead>
<tbody>
<tr>
  <td>テーブル</td>
  <td>クラス</td>
</tr>
<tr>
  <td>列、レコード</td>
  <td>オブジェクト</td>
</tr>
<tr>
  <td>フィールド、カラム</td>
  <td>プロパティ</td>
</tr>
</tbody>
</table>

<a name="retrieving.the.column.value" id="retrieving.the.column.value"></a><h3>カラムの値を検索する</h3>

<p>symfonyはモデルをビルドするとき、<code>schema.yml</code>内で定義されたそれぞれのテーブルに対して1つの基底オブジェクトクラスを作ります。それぞれのクラスはカラム定義に基づいたデフォルトのコンストラクター、アクセサー、ミューテーターを備えています: リスト8-7で示されるように、<code>new</code>、<code>getXXX()</code>、<code>setXXX()</code>メソッドはオブジェクトを作りオブジェクトのプロパティにアクセスすることを助けします。</p>

<p>リスト8-7 - 生成オブジェクトクラスのメソッド</p>

<pre class="yml">$article = new Article();
$article-&gt;setTitle('初めての記事');
$article-&gt;setContent('これは初めての記事です。\n 皆様が楽しんで下さることを祈っています！');
&nbsp;
$title   = $article-&gt;getTitle();
$content = $article-&gt;getContent();</pre>

<blockquote class="note"><p>
  生成されたオブジェクトクラスは<code>Article</code>と呼ばれ、<code>blog_article</code>テーブルに渡される<code>phpName</code>です。<code>phpName</code>がスキーマで定義されなかった場合、クラスは<code>BlogArticle</code>という名前になります。アクセサーとミューテーターはcamelCaseの方言のカラム名を使うので、<code>getTitle()</code>メソッドは<code>title</code>カラムの値を検索します。</p>
</blockquote>

<p>リスト8-8で示されるように、一度にいくつものフィールドを設定するには、それぞれのオブジェクトクラスに対して生成された、<code>fromArray()</code>メソッドを使用できます。</p>

<p>リスト8-8 - <code>fromArray()</code>メソッドは複数のセッターである</p>

<pre class="yml">$article-&gt;fromArray(array(
  'title'   =&gt; '初めての記事',
  'content' =&gt; 'これは初めての記事です。\n 皆様が楽しんで下さることを祈っています！',
));</pre>

<a name="retrieving.related.records" id="retrieving.related.records"></a><h3>関連するレコードを検索する</h3>

<p><code>blog_comment</code>テーブルの<code>article_id</code>カラムは明示的に外部キーを<code>blog_article</code>テーブルに定義します。それぞれのコメントは1つの記事に関連し、1つの記事は多くのコメントを持つことができます。生成されたクラスはつぎのようにこのリレーションをオブジェクト指向の方法に翻訳する5つのメソッドを含みます:</p>

<ul>
<li><code>$comment-&gt;getArticle()</code>: 関連する<code>Article</code>オブジェクトを取得するため</li>
<li><code>$comment-&gt;getArticleId()</code>: 関連する<code>Article</code>オブジェクトのIDを取得するため</li>
<li><code>$comment-&gt;setArticle($article)</code>: 関連する<code>Article</code>オブジェクトを定義するため</li>
<li><code>$comment-&gt;setArticleId($id)</code>: IDから関連する<code>Article</code>オブジェクトを定義するため</li>
<li><code>$article-&gt;getComments()</code>: 関連する<code>Comment</code>オブジェクトを取得するため</li>
</ul>

<p><code>getArticleId()</code>と<code>setArticleId()</code>メソッドは開発者が<code>article_id</code>カラムを通常のカラムと見なしてリレーションを手動で設定できることを示します。しかしこれらはあまり面白いものではありません。オブジェクト指向のアプローチの利点はほかの3つのメソッドで大いにあきらかになります。リスト8-9は生成されたセッターを使う方法を示します。</p>

<p>リスト8-9 - 外部キーは特別なセッターに翻訳される</p>

<pre class="yml">$comment = new Comment();
$comment-&gt;setAuthor('Steve');
$comment-&gt;setContent('うわ～、すごい、感動的だ: 最高の記事だよ！');
&nbsp;
// このコメントを以前の$articleオブジェクトに加える
$comment-&gt;setArticle($article);
&nbsp;
// 代替構文は
// オブジェクトがすでにデータベースに保存されている場合のみ意味をなす
$comment-&gt;setArticleId($article-&gt;getId());</pre>

<p>リスト8-10は生成されたゲッターを使う方法を示しています。これはモデルオブジェクトでメソッドチェーンを行うチェーンする方法も示しています。</p>

<p>リスト8-10 - 外部キーは特別なゲッターに翻訳される</p>

<pre class="yml">// 多対一のリレーション
echo $comment-&gt;getArticle()-&gt;getTitle();
 =&gt; 初めての記事
echo $comment-&gt;getArticle()-&gt;getContent();
 =&gt; これは初めての記事です。
    皆様が楽しんで下さることを祈っています！
&nbsp;
// 一対多のリレーション
$comments = $article-&gt;getComments();</pre>

<p><code>getArticle()</code>メソッドは<code>getTitle()</code>アクセサーから恩恵を受ける、<code>Article</code>クラスのオブジェクトを返します。これは開発者自身がJoinを行うよりもベターで、(<code>$comment-&gt;getArticleId()</code>の呼び出しから始まる)わずかな行のコードしか必要としません。</p>

<p>リスト8-10の<code>$comments</code>変数は<code>Comment</code>クラスのオブジェクトの配列を含みます。<code>$comments[0]</code>で最初のものを表示する、もしくは<code>foreach($comments as $comment)</code>によるコレクションを通して繰り返すことができます。</p>

<blockquote class="note"><p>
  モデルからのオブジェクトは規約によって単数形の名前で定義されるのはなぜなのかこれで理解できます。<code>blog_comment</code>テーブルで定義された外部キーによって<code>getComments()</code>メソッドが作成されます。<code>getComments()</code>メソッドの名前は<code>Comment</code>オブジェクトの名前に<code>s</code>を追加して名づけられたものです。モデルオブジェクトに複数形の名前をつけると、無意味な<code>getCommentss()</code>と命名されたメソッドが生成されることになります。</p>
</blockquote>

<a name="saving.and.deleting.data" id="saving.and.deleting.data"></a><h3>データの保存と削除を行う</h3>

<p><code>new</code>コンストラクターを呼び出すことで、新しいオブジェクトが作成されましたが、<code>blog_article</code>テーブルのなかには実際のレコードが作成されていません。オブジェクトを修正してもデータベースは何も影響を受けません。データをデータベースに保存するために、オブジェクトの<code>save()</code>メソッドを呼び出す必要があります。</p>

<pre class="yml">$article-&gt;save();</pre>

<p>ORMはオブジェクト間のリレーションを検出するほど賢いので、<code>$article</code>オブジェクトを保存することで関連する<code>$comment</code>オブジェクトも保存されます。symfonyは保存されたオブジェクトがデータベースのなかに既存の対応部分を持つことも知っているので、<code>save()</code>への呼び出しは時々<code>INSERT</code>もしくは<code>UPDATE</code>によってSQLに翻訳されます。主キーは<code>save()</code>メソッドによって自動的に設定されるので、保存した後に、<code>$article-&gt;getId()</code>によって新しい主キーを検索することができます。</p>

<blockquote class="tip"><p>
  <code>isNew()</code>を呼び出すことでオブジェクトが新しいかどうかをチェックできます。修正されたオブジェクトを保存すべきかどうか判断がつかないようでしたら、<code>isModified()</code>メソッドを呼び出してください。</p>
</blockquote>

<p>記事のコメントを読む場合、記事をインターネット上に公開することに関して気が変わることがあります。記事の評論家の皮肉が面白くないのであれば、リスト8-11で示されるように、<code>delete()</code>メソッドで簡単にコメントを削除できます。</p>

<p>リスト8-11 - 関連するオブジェクト上の<code>delete()</code>メソッドでデータベースからレコードを削除する</p>

<pre class="yml">foreach ($article-&gt;getComments() as $comment)
{
  $comment-&gt;delete();
}</pre>

<blockquote class="tip"><p>
  <code>delete()</code>メソッドを呼び出したあとでも、リクエストが終了するまでオブジェクトは利用できます。データベースのなかでオブジェクトが削除されることを確認するには、<code>isDeleted()</code>メソッドを呼び出してください。</p>
</blockquote>

<a name="retrieving.records.by.primary.key" id="retrieving.records.by.primary.key"></a><h3>主キーでレコードをとり出す</h3>

<p>特定のレコードの主キーを知っている場合、関連するオブジェクトを取得するにはピアクラスの<code>retrieveByPk()</code>クラスメソッドを使います。</p>

<pre class="yml">$article = ArticlePeer::retrieveByPk(7);</pre>

<p><code>schema.yml</code>ファイルは<code>id</code>フィールドを<code>blog_article</code>の主キーとして定義します。このステートメントは実際には<code>id</code>が7である記事を返します。主キーを使いましたので、あなたは1つのレコードだけが返されることを知っています; <code>$article</code>変数は<code>Article</code>クラスのオブジェクトを含みます。</p>

<p>いくつかの場合において、主キーは複数のカラムで構成されることがあります。そのような場合において、<code>retrieveByPK()</code>メソッドは複数のパラメーターをとり、それぞれの主キーのカラムに対してパラメーターは1つです。</p>

<p>生成された<code>retrieveByPKs()</code>メソッドを呼び出すことで、主キーに基づいて複数のオブジェクトを選ぶこともできます。<code>retireveByPKs()</code>メソッドはパラメーターとして主キーの配列を必要とします。</p>

<a name="retrieving.records.with.criteria" id="retrieving.records.with.criteria"></a><h3>Criteriaでレコードを検索する</h3>

<p>複数のレコードを検索したいとき、検索したいオブジェクトに対応するピアクラスの<code>doSelect()</code>メソッドを呼び出す必要があります。たとえば、<code>Article</code>クラスのオブジェクトを検索するには、<code>ArticlePeer::doSelect()</code>を呼び出します。</p>

<p><code>doSelect()</code>メソッドの最初のパラメーターは<code>Criteria</code>クラスのオブジェクトです。<code>Criteria</code>クラス(訳注：日本語で「基準」を意味する)はデータベースの抽象化のためにSQLなしで定義されたシンプルなクエリの定義クラスです。</p>

<p>空の<code>Criteria</code>はすべてのクラスのオブジェクトを返します。たとえば、リスト8-12で示されるコードはすべての記事を検索します。</p>

<p>リスト8-12 - 空のCriteria -- <code>doSelect()</code>を持つCriteriaでレコードを検索する</p>

<pre class="yml">$c = new Criteria();
$articles = ArticlePeer::doSelect($c);
&nbsp;
// 上記のコードはつぎのSQLクエリになります
SELECT blog_article.ID, blog_article.TITLE, blog_article.CONTENT,
       blog_article.CREATED_AT
FROM   blog_article;</pre>

<blockquote class="sidebar"><p class="title">
  ハイドレイティング(hydrating)</p>
  
  <p><code>::doSelect()</code>への呼び出しは実際にはシンプルなSQLクエリよりはるかに強力です。最初に、SQLは選択したDBMSのために最適化されます。2番目に、<code>Criteria</code>に渡されるどの値もSQLコードに統合されるまえにエスケープされ、SQLインジェクションのリスクが予防されます。3番目に、メソッドは、結果セットではなく、オブジェクトの配列を返します。ORMはデータベースの結果セットに基づいてオブジェクトを自動的に作成し投入します。このプロセスはハイドレイティング(hydrating)と呼ばれます。</p>
</blockquote>

<p>より複雑なオブジェクトを選択するには、<code>WHERE</code>、<code>ORDER BY</code>、<code>GROUP BY</code>、およびほかのSQLステートメントと同等のものが必要です。<code>Criteria</code>オブジェクトはこれらすべての条件のためのメソッドとパラメーターを持ちます。たとえば、リスト8-13のように、Steveによって書かれ、日付順に並べられた、すべてのコメントを取得するには、<code>Criteria</code>をビルドします。</p>

<p>リスト8-13 - <code>doSelect()</code>を持つ<code>Criteria</code>によってレコードを検索する -- <code>Criteria</code>は条件つき</p>

<pre class="yml">$c = new Criteria();
$c-&gt;add(CommentPeer::AUTHOR, 'Steve');
$c-&gt;addAscendingOrderByColumn(CommentPeer::CREATED_AT);
$comments = CommentPeer::doSelect($c);
&nbsp;
// 上記のコードはつぎのようなSQLクエリになる
SELECT blog_comment.ARTICLE_ID, blog_comment.AUTHOR, blog_comment.CONTENT,
       blog_comment.CREATED_AT
FROM   blog_comment
WHERE  blog_comment.author = 'Steve'
ORDER BY blog_comment.CREATED_AT ASC;</pre>

<p><code>add()</code>メソッドへのパラメーターとして渡されるクラスの定数はプロパティ名を参照します。これらの定数はカラム名の大文字バージョンから名づけられます。たとえば、<code>blog_article</code>テーブルの<code>content</code>カラムを扱うには、<code>ArticlePeer::CONTENT</code>クラス定数を使います。</p>

<blockquote class="note"><p>
  なぜ<code>blog_comment.AUTHOR</code>の代わりに<code>CommentPeer::AUTHOR</code>を使うのか？ SQLクエリに出力される方法はどちらなのか？データベースの<code>author</code>フィールドの名前を<code>contributor</code>に変更する必要がある場合を考えてみましょう。<code>blog_comment.AUTHOR</code>を使う場合、すべての呼び出しへのモデルを変更しなければなりません。一方で、<code>CommentPeer::AUTHOR</code>を使う場合、<code>schema.yml</code>内のカラム名を変更し、<code>phpName</code>を<code>AUTHOR</code>として保存し、モデルをリビルドする必要があるだけです。</p>
</blockquote>

<p>テーブル8-1はSQLの構文と<code>Criteria</code>オブジェクトの構文を比較します。</p>

<p>テーブル8-1 - SQLの構文と<code>Criteria</code>オブジェクトの構文</p>

<table>
<thead>
<tr>
  <th>SQL</th>
  <th>Criteria</th>
</tr>
</thead>
<tbody>
<tr>
  <td><code>WHERE column = value</code></td>
  <td><code>-&gt;add(column, value);</code></td>
</tr>
<tr>
  <td><code>WHERE column &lt;&gt; value</code></td>
  <td><code>-&gt;add(column, value, Criteria::NOT_EQUAL);</code></td>
</tr>
<tr>
  <td><strong>ほかの比較演算子</strong></td>
  <td></td>
</tr>
<tr>
  <td><code>&gt; , &lt;</code></td>
  <td><code>Criteria::GREATER_THAN, Criteria::LESS_THAN</code></td>
</tr>
<tr>
  <td><code>&gt;=, &lt;=</code></td>
  <td><code>Criteria::GREATER_EQUAL, Criteria::LESS_EQUAL</code></td>
</tr>
<tr>
  <td><code>IS NULL, IS NOT NULL</code></td>
  <td><code>Criteria::ISNULL, Criteria::ISNOTNULL</code></td>
</tr>
<tr>
  <td><code>LIKE, ILIKE</code></td>
  <td><code>Criteria::LIKE, Criteria::ILIKE</code></td>
</tr>
<tr>
  <td><code>IN, NOT IN</code></td>
  <td><code>Criteria::IN, Criteria::NOT_IN</code></td>
</tr>
<tr>
  <td><strong>ほかのSQLキーワード</strong></td>
  <td></td>
</tr>
<tr>
  <td><code>ORDER BY column ASC</code></td>
  <td><code>-&gt;addAscendingOrderByColumn(column);</code></td>
</tr>
<tr>
  <td><code>ORDER BY column DESC</code></td>
  <td><code>-&gt;addDescendingOrderByColumn(column);</code></td>
</tr>
<tr>
  <td><code>LIMIT limit</code></td>
  <td><code>-&gt;setLimit(limit)</code></td>
</tr>
<tr>
  <td><code>OFFSET offset</code></td>
  <td><code>-&gt;setOffset(offset)</code></td>
</tr>
<tr>
  <td><code>FROM table1, table2 WHERE table1.col1 = table2.col2</code></td>
  <td><code>-&gt;addJoin(col1, col2)</code></td>
</tr>
<tr>
  <td><code>FROM table1 LEFT JOIN table2 ON table1.col1 = table2.col2</code></td>
  <td><code>-&gt;addJoin(col1, col2, Criteria::LEFT_JOIN)</code></td>
</tr>
<tr>
  <td><code>FROM table1 RIGHT JOIN table2 ON table1.col1 = table2.col2</code></td>
  <td><code>-&gt;addJoin(col1, col2, Criteria::RIGHT_JOIN)</code></td>
</tr>
</tbody>
</table>

<blockquote class="tip"><p>
  生成されたクラスで利用可能なメソッドがどれなのか見つけて理解するためのベストの方法は、生成後に<code>lib/model/om/</code>フォルダーのなかの<code>Base</code>ファイルを見ることです。メソッドの名前はとても明白ですが、これらに関する詳細なコメントが必要な場合、<code>config/propel.ini</code>ファイル内の<code>propel.builder.addComments</code>パラメーターを<code>true</code>に設定して、モデルをリビルドします。</p>
</blockquote>

<p>リスト8-14は複数の条件を持つ<code>Criteria</code>のほかの例を示します。日付順に並べ替えられた"enjoy"の単語を含む記事上のSteveによるすべてのコメントを検索します。</p>

<p>リスト8-14 - <code>doSelect()</code>を持つ<code>Criteria</code>によってレコードを検索する別の例　-- <code>Criteria</code>は条件つき</p>

<pre class="yml">$c = new Criteria();
$c-&gt;add(CommentPeer::AUTHOR, 'Steve');
$c-&gt;addJoin(CommentPeer::ARTICLE_ID, ArticlePeer::ID);
$c-&gt;add(ArticlePeer::CONTENT, '%enjoy%', Criteria::LIKE);
$c-&gt;addAscendingOrderByColumn(CommentPeer::CREATED_AT);
$comments = CommentPeer::doSelect($c);
&nbsp;
// 上記のコードはつぎのようなSQLクエリになる
SELECT blog_comment.ID, blog_comment.ARTICLE_ID, blog_comment.AUTHOR,
       blog_comment.CONTENT, blog_comment.CREATED_AT
FROM   blog_comment, blog_article
WHERE  blog_comment.AUTHOR = 'Steve'
       AND blog_article.CONTENT LIKE '%enjoy%'
       AND blog_comment.ARTICLE_ID = blog_article.ID
ORDER BY blog_comment.CREATED_AT ASC</pre>

<p>SQLはとても複雑なクエリを開発できるシンプルな言語なので、<code>Criteria</code>オブジェクトはどんな複雑なレベルの条件を処理できます。しかし、多くの開発者は条件をオブジェクト指向のロジックに翻訳するまえに最初にSQLを考えるので、<code>Criteria</code>を最初に把握するのは難しいでしょう。これを理解するベストの方法は具体例とサンプルのアプリケーションから学ぶことです。たとえば、symfonyのプロジェクトのWebサイトは多くの方法であなたを啓発する<code>Criteria</code>の開発例で満たされています。</p>

<p><code>doSelect()</code>メソッドに加えて、すべてのピアクラスは<code>doCount()</code>メソッドを持ちます。<code>doCount()</code>メソッドはパラメーターとして渡された基準を満たすレコードの数をそのままカウントして、カウント数を整数として返します。この場合、返すオブジェクトが存在しないので、ハイドレイティングの処理は行われません。また<code>doCount()</code>メソッドは<code>doSelect()</code>よりも速いです。</p>

<p>ピアクラスは<code>Criteria</code>をパラメーターとして必要とする<code>doDelete()</code>、<code>doInsert()</code>と<code>doUpdate()</code>メソッドも提供します。これらのメソッドによって<code>DELETE</code>クエリ、<code>INSERT</code>クエリ、と<code>UPDATE</code>クエリをデータベースに発行できます。これらのPropelのメソッドの詳細に関しては生成されたモデルのピアクラスを確認してください。</p>

<p>最後に、最初に返されたオブジェクトが欲しい場合、<code>doSelect()</code>をすべて<code>doSelectOne()</code>呼び出しで置き換えます。これは<code>Criteria</code>が1つの結果だけを返すことを知っているときにあてはまる場合で、利点はこのメソッドがオブジェクトの配列ではなくオブジェクトを返すことです。</p>

<blockquote class="tip"><p>
  <code>doSelect()</code>クエリが多数の結果を返すとき、レスポンスのなかでその部分集合だけを表示したいことがあります。symfonyは結果のパジネーションを自動化する<code>sfPropelPager</code>と呼ばれるページャークラスを提供します。もっと詳しい情報と使いかたの例は<a href="http://www.symfony-project.org/cookbook/1_1/pager">http://www.symfony-project.org/cookbook/1_1/pager</a>のAPIドキュメントで確認してください。</p>
</blockquote>

<a name="using.raw.sql.queries" id="using.raw.sql.queries"></a><h3>生のSQLクエリを使う</h3>

<p>ときどき、オブジェクトを検索する必要はないが、データベースによって算出された総合的な結果だけが欲しいことがあります。たとえば、すべての記事の最新の作成日時を取得するために、すべての記事を検索し、配列でループしても無意味です。結果だけを返すようにデータベースに求めるほうが望ましいです。なぜなら、これはオブジェクトのハイドレイティングの処理をスキップするからです。</p>

<p>一方で、データベース抽象化の利点を失いたくないので、データベース管理のためにPHPのコマンドを直接呼び出したくない場合があります。これはORM(Propel)を回避し、データベースの抽象化(Creole)を回避しないことが必要であることを意味します。</p>

<p>Creoleでデータベースにクエリを行うにはつぎの作業を行う必要があります:</p>

<ol>
<li>データベースの接続を取得する。</li>
<li>クエリの文字列をビルドする。</li>
<li>それからステートメントを作る。</li>
<li>ステートメントの実行から得られた結果セットをイテレートする</li>
</ol>

<p>何を言っているのかよくわからないのでしたら、おそらくリスト8-15のコードを見ればより明確になるでしょう。</p>

<p>リスト8-15 - CreoleでカスタムSQLクエリ</p>

<pre class="yml">$connection = Propel::getConnection();
$query = 'SELECT MAX(%s) AS max FROM %s';
$query = sprintf($query, ArticlePeer::CREATED_AT, ArticlePeer::TABLE_NAME);
$statement = $connection-&gt;prepareStatement($query);
$resultset = $statement-&gt;executeQuery();
$resultset-&gt;next();
$max = $resultset-&gt;getInt('max');</pre>

<p>PropelのSelect機能と同じように、Creoleのクエリを使い始めたときこれらは扱いにくいです。繰り返しますが、既存のアプリケーションとチュートリアルからの例は正しい方法を示します。</p>

<blockquote class="caution"><p>
  このプロセスを回避しデータベースに直接アクセスする場合、Creoleによって提供されたセキュリティと抽象化を失うリスクを負うことになります。Creoleの方法は長いですが、パフォーマンス、ポータビリティ、アプリケーションのセキュリティを保証するよい習慣が強制されます。これは信用できないソース(たとえばインターネットのユーザー)からのパラメーターを含むクエリにとりわけあてはまります。Creoleは必要なすべてのエスケープを行い、データベースを安全にします。データベースに直接アクセスすることはSQLインジェクション攻撃のリスクが存在する状態に晒されることを意味します。</p>
</blockquote>

<a name="using.special.date.columns" id="using.special.date.columns"></a><h3>特別な日付カラムを使う</h3>

<p>通常、テーブルが<code>created_at</code>と呼ばれるカラムを持つとき、レコードの作成日時のタイムスタンプを保存するためにこのカラムは使われます。同じことが<code>updated_at</code>カラムにもあてはまります。レコード自身が更新されるたびに現在の時間の値に更新されます。</p>

<p>吉報はsymfonyがこれらのカラムを認識し更新を扱うことです。<code>created_at</code>カラムと<code>updated_at</code>カラムを手動で設定する必要はありません; リスト8-16で示されるように、これらは自動的に更新されます。同じことが<code>created_on</code>と<code>updated_on</code>カラムにもあてはまります。</p>

<p>リスト8-16 - <code>created_at</code>と<code>updated_at</code>カラムは自動的に処理される</p>

<pre class="yml">$comment = new Comment();
$comment-&gt;setAuthor('Steve');
$comment-&gt;save();
&nbsp;
// 作成時点の日付を表示する
echo $comment-&gt;getCreatedAt();
  =&gt; [date of the database INSERT operation]</pre>

<p>加えて、日付カラムのためのゲッターは日付フォーマットを引数として受けとります:</p>

<pre class="yml">echo $comment-&gt;getCreatedAt('Y-m-d');</pre>

<blockquote class="sidebar"><p class="title">
  <strong>データレイヤーへのリファクタリング</strong></p>
  
  <p>symfonyを開発しているとき、アクションのドメインロジックのコードを書くことが始まるのがよくあります。しかしながらデータベースクエリとモデル操作のコードはコントローラーレイヤーに保存すべきではなく、データに関連するすべてのロジックはモデルレイヤーに移動させるべきです。アクションの複数の場所で同じリクエストを行う必要があるときは、関連コードをモデルに移動させることを考えてください。この作業を行うことでアクションのコードを短くて読みやすい状態に保つための助けになります。</p>
  
  <p>たとえば、blogで(リクエストパラメーターとして渡される)任意のタグに対してもっとも人気のある記事を検索するために必要なコードを想像してください。このコードはアクションのなかには存在しませんが、モデルのなかに存在します。実際、テンプレートのなかでこの記事の一覧を表示する必要がある場合、アクションはつぎのようなシンプルなものになります:</p>

<pre class="yml">public function executeShowPopularArticlesForTag($request)
{
  $tag = TagPeer::retrieveByName($request-&gt;getParameter('tag'));
  $this-&gt;foward404Unless($tag);
  $this-&gt;articles = $tag-&gt;getPopularArticles(10);
}</pre>
  
  <p>アクションはリクエストパラメーターから<code>Tag</code>クラスのオブジェクトを作ります。それからデータベースにクエリを行うために必要なすべてのコードはこのクラスの<code>getPopularArticles()</code>メソッドに設置されます。これによってアクションはより読みやすくなり、モデルのコードは別のアクションのなかで簡単に再利用できます。</p>
  
  <p>コードをより適切な場所に移動させることはリファクタリングの技術の1つです。頻繁にこの作業を行えば、コードは維持しやすくほかの開発者にわかりやすくなります。データレイヤーにリファクタリングを行うときのよい経験則はアクションのコードに含まれるPHPのほとんどのコードが10行を越えないことです。</p>
</blockquote>

<a name="database.connections" id="database.connections"></a><h2>データベースの接続</h2>

<p>データモデルは利用されるデータベースから独立していますが、間違いなくデータベースを使うことになります。リクエストをプロジェクトのデータベースに送信するためにsymfonyに求められる最小限の情報は名前、クレデンシャル、とデータベースのタイプです。これらの接続設定はデータソース名(DSN Data Source Name)を<code>configure:database</code>タスクに渡すことで設定可能です:</p>

<pre><code>&gt; php symfony configure:database "mysql://login:passwd@localhost/blog"
</code></pre>

<p>接続設定は環境に依存しています。アプリケーションの<code>prod</code>、<code>dev</code>、と<code>test</code>環境もしくは<code>env</code>オプションの使用による別の環境に対して異なる設定を定義できます:</p>

<pre><code>&gt; php symfony --env=prod configure:database "mysql://login:passwd@localhost/blog"
</code></pre>

<p>この設定はアプリケーションごとにオーバーライドすることもできます。たとえば、フロントエンドとバックエンドのアプリケーションに対して異なるセキュリティポリシーを適用し、データベースを扱うために1つのデータベースのなかで異なる権限を持つ複数のデータベースユーザーを定義するために、このアプローチを利用できます:</p>

<pre><code>&gt; php symfony --app=frontend configure:database "mysql://login:passwd@localhost/blog"
</code></pre>

<p>それぞれの環境に対して、多くの接続を定義できます。それぞれの接続は同じ名前でラベルづけされたスキーマを参照します。デフォルトで使われる接続名は<code>propel</code>でこれはリスト8-3の<code>propel</code>スキーマを参照します。<code>name</code>オプションによって別の接続を作成することができます:</p>

<pre><code>&gt; php symfony --name=main configure:database "mysql://login:passwd@localhost/blog"
</code></pre>

<p><code>config/</code>ディレクトリに設置された<code>databases.yml</code>ファイルのなかでこれらの接続設定を手動で入力することもできます。リスト8-17はファイルの例を示しリスト8-18は拡張された表記方法による同じ例を示します。</p>

<p>リスト8-17 - 省略記法のデータベース接続設定</p>

<pre class="yml">all:
  propel:
    class:          sfPropelDatabase
    param:
      dsn:          mysql://login:passwd@localhost/blog</pre>

<p>リスト8-18 - データベース接続設定のサンプル(<code>myproject/config/databases.yml</code>)</p>

<pre class="yml">prod:
  propel:
    param:
      hostspec:           mydataserver
      username:           myusername
      password:           xxxxxxxxxx
&nbsp;
all:
  propel:
    class:                sfPropelDatabase
    param:
      phptype:            mysql     # データベースのベンダー
      hostspec:           localhost
      database:           blog
      username:           login
      password:           passwd
      port:               80
      encoding:           utf8      # テーブル作成のためのデフォルトの文字集合
      persistent:         true      # 永続的接続を使う</pre>

<p><code>phptype</code>パラメーターの認められる値はCreoleによってサポートされるデータベースシステムの1つです:</p>

<ul>
<li><code>mysql</code></li>
<li><code>mssql</code></li>
<li><code>pgsql</code></li>
<li><code>sqlite</code></li>
<li><code>oracle</code></li>
</ul>

<p><code>hostspec</code>、<code>database</code>、<code>username</code>、と<code>password</code>は通常はデータベース接続設定です。</p>

<p>アプリケーションごとに設定をオーバーライドするために、 <code>apps/frontend/config/databases.yml</code>のようなアプリケーション固有のファイルを編集する必要があります。</p>

<p>SQLiteのデータベースを使う場合、<code>hostspec</code>パラメーターはデータベースファイルのパスに設定しなければなりません。たとえば、blogのデータベースを<code>data/blog.db</code>に保存する場合、<code>databases.yml</code>ファイルはリスト8-19のようになります。</p>

<p>リスト8-19 - SQliteのためのデータベース接続設定はファイルパスをホストとして使う</p>

<pre class="yml">all:
  propel:
    class:          sfPropelDatabase
    param:
      phptype:  sqlite
      database: %SF_DATA_DIR%/blog.db</pre>

<a name="extending.the.model" id="extending.the.model"></a><h2>モデルを拡張する</h2>

<p>生成されたモデルメソッドはすばらしいものですが、十分ではないことはよくあることです。独自のビジネスロジックを実装すると同時に、新しいメソッドを追加するか既存のメソッドをオーバーライドすることで、ビジネスロジックを拡張する必要があります。</p>

<a name="adding.new.methods" id="adding.new.methods"></a><h3>新しいメソッドを追加する</h3>

<p>新しいメソッドを<code>lib/model/</code>ディレクトリのなかに生成された空のモデルクラスに追加できます。現在のオブジェクトのメソッドを呼び出すには<code>$this</code>を使い、現在のクラスの静的メソッドを呼び出すには<code>self::</code>を使います。カスタムクラスが<code>lib/model/om/</code>ディレクトリのなかに設置された<code>Base</code>クラスからメソッドを継承することを覚えておいてください。</p>

<p>たとえば、リスト8-20で示されるように、リスト8-3に基づいて生成された<code>Article</code>オブジェクトに対して、<code>Article</code>クラスのオブジェクトをechoすることでタイトルを表示できるように、<code>__toString()</code>マジックメソッドを追加できます。</p>

<p>リスト8-20 - モデルをカスタマイズする(<code>lib/model/Article.php</code>)</p>

<pre class="yml">class Article extends BaseArticle
{
  public function __toString()
  {
    return $this-&gt;getTitle();  // getTitle()はBaseArticleから継承される
  }
}</pre>

<p>ピアクラスを拡張することもできます。たとえば、リスト8-21で示されるように、記事作成の日付順で並べられたすべての記事を検索するにはメソッドを追加します。</p>

<p>リスト8-21 - モデルをカスタマイズする(<code>lib/model/ArticlePeer.php</code>)</p>

<pre class="yml">class ArticlePeer extends BaseArticlePeer
{
  public static function getAllOrderedByDate()
  {
    $c = new Criteria();
    $c-&gt;addAscendingOrderByColumn(self::CREATED_AT);
    return self::doSelect($c);
&nbsp;
  }
}</pre>

<p>リスト8-22で示されるように、新しいメソッドは生成されたメソッドと同じ方法で利用できます。</p>

<p>リスト8-22 -カスタムモデルメソッドを利用することは生成されたメソッドを利用することと似ている</p>

<pre class="yml">foreach (ArticlePeer::getAllOrderedByDate() as $article)
{
  echo $article;      // __toString()マジックメソッドを呼び出す
}</pre>

<a name="overriding.existing.methods" id="overriding.existing.methods"></a><h3>既存のメソッドをオーバーライドする</h3>

<p><code>Baseクラス</code>内部の生成されたいくつかのメソッドがあなたの要件に合わない場合、これらのメソッドをカスタムクラスでオーバーライドすることもできます。同じメソッドのシグネイチャ(すなわち、同じ数の引数)を使っていることを確認してください。</p>

<p>たとえば、<code>$article-&gt;getComments()</code>メソッドは<code>Comment</code>オブジェクトの配列を順不同で返します。最新のコメントが一番最初になるように作成時の日付順でコメントを並べたい場合、リスト8-23で示されるように<code>getComments()</code>メソッドをオーバーライドします。オリジナルの<code>getComments()</code>メソッド(<code>lib/model/om/BaseArticle.php</code>で見つかる)はパラメーターとして基準の値と接続の値が必要なので、あなたの関数が同じことを行わなければならないことに注意してください。</p>

<p>リスト8-23 - 既存のモデルメソッドをオーバーライドする(<code>lib/model/Article.php</code>)</p>

<pre class="yml">public function getComments($criteria = null, $con = null)
{
  if (is_null($criteria))
  {
    $criteria = new Criteria();
  }
  else
  {
    // PHP 5ではオブジェクトは参照で渡されるので、オリジナルを修正することを避けるには、cloneしなければならない
    $criteria = clone $criteria;
  }
  $criteria-&gt;addDescendingOrderByColumn(CommentPeer::CREATED_AT);
&nbsp;
  return parent::getComments($criteria, $con);
}</pre>

<p>カスタムメソッドは最終的に親の<code>Base</code>クラスの1つを呼び出します。これはよい習慣です。しかしながら、完全にそれを回避し、望む結果を返すことができます。</p>

<a name="using.model.behaviors" id="using.model.behaviors"></a><h3>モデルのビヘイビアーを使う</h3>

<p>いくつかのモデルを修正したものは一般的で再利用できます。たとえば、モデルオブジェクトをソート可能にしてオブジェクトの保存が同時に起きることを防止する楽観的ロック(オプティミスティックロック)にすることは多くのクラスに追加できる一般的な拡張機能です。</p>

<p>symfonyはこれらの拡張機能をビヘイビアーにまとめます。ビヘイビアー(behavior)とは追加メソッドをモデルクラスに提供する外部クラスです。モデルクラスはすでにフックを含み、symfonyは<code>sfMixer</code>(詳細は17章を参照)の方法によってビヘイビアーを拡張する方法を知っています。</p>

<p>モデルクラスのビヘイビアーを有効にするには、<code>config/propel.ini</code>ファイルの設定の1つを修正しなければなりません:</p>

<pre><code>propel.builder.AddBehaviors = true     // デフォルト値はfalse
</code></pre>

<p>symfonyにデフォルトで搭載されているビヘイビアーは存在しませんが、それらはプラグインを通してインストールできます。いったんビヘイビアーのプラグインがインストールされると、クラスを1行でビヘイビアーに割り当てることができます。たとえば、<code>sfPropelParanoidBehaviorPlugin</code>をアプリケーションにインストールする場合、<code>Article.class.php</code>の最後の行につぎのコードを追加すればこのビヘイビアーを持つ<code>Article</code>クラスを拡張できます:</p>

<pre class="yml">sfPropelBehavior::add('Article', array(
  'paranoid' =&gt; array('column' =&gt; 'deleted_at')
));</pre>

<p>モデルをリビルドしたあとで、<code>sfPropelParanoidBehavior::disable()</code>でビヘイビアーを一時的に無効にしないかぎり、削除された<code>Article</code>オブジェクトはORMを使うクエリには見えないだけで、データベースに保存されたままになります。</p>

<p><strong>symfony 1.1の新しい機能</strong>: 代わりに、<code>_behaviors</code>キーの下にビヘイビアーの一覧を追加することで<code>schema.yml</code>のなかで直接ビヘイビアーを宣言することもできます(つぎのリスト8-34を参照)。</p>

<p>ビヘイビアーを見つけるにはwikiにあるsymfonyのプラグインのリストをチェックしてください(<a href="http://trac.symfony-project.org/wiki/SymfonyPlugins#Propelbehaviorplugins">http://trac.symfony-project.org/wiki/SymfonyPlugins#Propelbehaviorplugins</a>)。それぞれのプラグインには独自のドキュメントとインストールガイドがあります。</p>

<a name="extended.schema.syntax" id="extended.schema.syntax"></a><h2>スキーマの拡張構文</h2>

<p>リスト8-3で示されるように、<code>schema.yml</code>ファイルをシンプルにすることができます。しかしながらリレーショナルモデルは複雑であることがよくあります。それがスキーマがほとんどすべての場合を扱うことができる拡張された構文を持つ理由です。</p>

<a name="attributes" id="attributes"></a><h3>属性</h3>

<p>リスト8-24で示されるように、接続とテーブルは固有の属性を持つことができます。これらは<code>_attributes</code>キーの下で設定します。</p>

<p>リスト8-24 - 接続とテーブルのための属性</p>

<pre class="yml">propel:
  _attributes:   { noXsd: false, defaultIdMethod: none, package: lib.model }
  blog_article:
    _attributes: { phpName: Article }</pre>

<p>コード生成が行われるまえにスキーマを検証したい場合を考えます。これを行うには、接続に対して<code>noXSD</code>属性を無効にします。接続は<code>defaultIdMethod</code>属性もサポートします。何も提供されない場合、IDを生成するデータベースのネイティブなメソッドが使われます。たとえば、MySQLに対しては<code>autoincrement</code>、PostgreSQLに対しては<code>sequences</code>です。ほかのとりうる値は<code>none</code>です。</p>

<p><code>package</code>属性は名前空間のようなものです; これは生成されたクラスが保存される場所のパスを決めます。デフォルト値は<code>lib/model/</code>ですが、サブパッケージのモデルを編成するために変更できます。たとえば、コアのビジネスクラスとデータベースに保存された統計エンジンを定義するクラスを同じディレクトリのなかで混在させたくない場合、<code>lib.model.business</code>パッケージと<code>lib.model.stats</code>パッケージで2つのスキーマを定義してください。</p>

<p>テーブルをマッピングする生成クラスの名前を設定するために使われる、<code>phpName</code>テーブル属性はすでに見ました。</p>

<p>リスト8-25で示されるように、ローカライズされた内容を含むテーブル(すなわち、国際化のために、関連するテーブルのなかに存在する、複数のバージョンの内容)も2つの追加属性をとります(詳細は13章を参照)。</p>

<p>リスト8-25 - 国際化テーブルのための属性</p>

<pre class="yml">propel:
  blog_article:
    _attributes: { isI18N: true, i18nTable: db_group_i18n }</pre>

<blockquote class="sidebar"><p class="title">
  <strong>複数のスキーマを扱う</strong></p>
  
  <p>アプリケーションごとに複数のスキーマを持つことができます。symfonyは<code>config/</code>フォルダーの<code>schema.yml</code>もしくは<code>schema.yml</code>で終わるすべてのファイルを考慮に入れます。アプリケーションが多くのテーブルを持つ場合、もしくはテーブルが同じ接続を共有しない場合、このアプローチがとても便利であることがわかります。</p>
  
  <p>つぎの2つのスキーマを考えてください:</p>

<pre><code> [yml]
 // config/business-schema.ymlにおいて
 propel:
   blog_article:
     _attributes: { phpName: Article }
   id:
   title: varchar(50)

 // config/stats-schema.ymlにおいて
 propel:
   stats_hit:
     _attributes: { phpName: Hit }
   id:
   resource: varchar(100)
   created_at:
</code></pre>
  
  <p>同じ接続を共有する両方のスキーマ(<code>propel</code>)と、<code>Article</code>クラスと<code>Hit</code>クラスは同じ<code>lib/model/</code>ディレクトリのもとで生成されます。あたかも1つだけのスキーマを書いたようにすべての物事が行われます。</p>
  
  <p>異なる接続(たとえば、<code>databases.yml</code>のなかで定義される<code>propel</code>と<code>propel_bis</code>)を使う異なるスキーマを持つことが可能で生成クラスをサブディレクトリに分類できます。</p>

<pre><code> [yml]
 // config/business-schema.ymlにおいて
 propel:
   blog_article:
     _attributes: { phpName: Article, package: lib.model.business }
   id:
   title: varchar(50)

 // config/stats-schema.ymlにおいて
 propel_bis:
   stats_hit:
     _attributes: { phpName: Hit, package: lib.model.stat }
   id:
   resource: varchar(100)
   created_at:
</code></pre>
  
  <p>多くのアプリケーションは複数のスキーマを使います。とりわけ、プラグインのなかにはアプリケーション独自のクラスに干渉しないようにプラグイン独自のスキーマとパッケージを持つものがあります(詳細は17章を参照)。</p>
</blockquote>

<a name="column.details" id="column.details"></a><h3>カラムの詳細</h3>

<p>基本構文は選択肢を2つ与えてくれます; (空の値を渡すことで)symfonyに名前からカラムの特徴を推測させるか、1つの<code>type</code>キーワードで型を定義するかです。リスト8-26はこれらの選択肢のお手本を示しています。</p>

<p>リスト8-26 - 基本的なカラム属性</p>

<pre class="yml">propel:
  blog_article:
    id:                 # symfonyに仕事を任せる
    title: varchar(50)  # あなた自身が型を指定する</pre>

<p>しかしながら、カラムに対してもっと多くのことを定義できます。もし行う場合、リスト8-27で示されるように、カラムの設定を連想配列として定義する必要があります。</p>

<p>リスト8-27 - 複雑なカラム属性</p>

<pre class="yml">propel:
  blog_article:
    id:       { type: integer, required: true, primaryKey: true, autoIncrement: true }
    name:     { type: varchar(50), default: foobar, index: true }
    group_id: { type: integer, foreignTable: db_group, foreignReference: id, onDelete: cascade }</pre>

<p>カラムのパラメーターはつぎのとおりです:</p>

<ul>
<li><code>type</code>: カラムの型。選択肢は<code>boolean</code>、<code>tinyint</code>、<code>smallint</code>、<code>integer</code>、<code>bigint</code>、<code>double</code>、<code>float</code>、<code>real</code>、<code>decimal</code>、<code>char</code>、<code>varchar(size)</code>、<code>longbarchar</code>、<code>date</code>、<code>time</code>、<code>timestamp</code>、<code>bu_date</code>、<code>bu_timestamp</code>、<code>blob</code>、と<code>clob</code>です。</li>
<li><code>required</code>: ブール値。カラムをrequiredにしたい場合これを<code>true</code>に設定します。</li>
<li><code>size</code>: 型がサポートするフィールドのサイズもしくは長さ</li>
<li><code>scale</code>: decimalデータ型使用のための小数位(sizeも指定しなければなりません)</li>
<li><code>default</code>: デフォルト値。</li>
<li><code>primaryKey</code>: ブール値。主キーに対してこれを<code>true</code>に設定します。</li>
<li><code>autoIncrement</code>: ブール値。オートインクリメントされた値を取る必要のある<code>integer</code>型のカラムに対してこれを<code>true</code>に設定します。</li>
<li><code>sequence</code>: <code>autoIncrement</code>カラムに対してシーケンスを使うデータベース(たとえばPostgreSQL、Oracle)のためのシーケンス名。</li>
<li><code>index</code>: ブール値。シンプルなインデックスが欲しい場合は<code>true</code>に、カラムにユニークなインデックスを作りたい場合は<code>unique</code>に設定します。</li>
<li><code>foreignTable</code>: 別のテーブルに外部キーを作るために使われる、テーブル名。</li>
<li><code>foreignReference</code>: <code>foreingTable</code>経由で外部キーが定義された場合の関連カラムの名前。</li>
<li><code>onDelete</code>: 関連テーブルに存在するレコードが削除されたときにアクションを起動させるために指定します。<code>setnull</code>に設定したとき、外部キーのカラムは<code>null</code>に設定されます。<code>cascade</code>に設定したとき、レコードは削除されます。データベースエンジンがsetのビヘイビアーをサポートしない場合、ORMがエミュレートします。これは<code>foreignTable</code>と<code>foreingReference</code>を持つカラムだけに該当します。</li>
<li><code>isCulture</code>: ブール値。ローカライズされた内容テーブルにあるcultureのカラムに対してこれを<code>true</code>に設定してください(13章を参照)。</li>
</ul>

<a name="foreign.keys" id="foreign.keys"></a><h3>外部キー</h3>

<p><code>foreignTable</code>と<code>foreignReference</code>カラム属性の代わりに、外部キーをテーブルの<code>_foreignKeys:</code>キーの下に追加できます。リスト8-28のスキーマは<code>blog_user</code>テーブルの<code>id</code>カラムにマッチする、<code>user_id</code>カラムの上に外部キーを作ります</p>

<p>リスト8-28 - 外部キーの代替構文</p>

<pre class="yml">propel:
  blog_article:
    id:
    title:   varchar(50)
    user_id: { type: integer }
    _foreignKeys:
      -
        foreignTable: blog_user
        onDelete:     cascade
        references:
          - { local: user_id, foreign: id }</pre>

<p>リスト8-29で示されるように、この代替構文は複数参照を持つ外部キーに対して外部キーに名前を与えるために役立ちます。</p>

<p>リスト8-29 - 複数参照の外部キーに適用された外部キーの代替構文</p>

<pre><code>    _foreignKeys:
      my_foreign_key:
        foreignTable:  db_user
        onDelete:      cascade
        references:
          - { local: user_id, foreign: id }
          - { local: post_id, foreign: id }
</code></pre>

<a name="indexes" id="indexes"></a><h3>インデックス</h3>

<p><code>index</code>カラム属性の代わりに、テーブル内の<code>_indexes:</code>キーの下にインデックスを追加できます。ユニークインデックスを定義したい場合、<code>_uniques:</code>ヘッダーを代わりに使わなければなりません。リスト8-30はインデックスのための代替構文を示しています。</p>

<p>リスト8-30 - インデックスとユニークインデックスの代替構文</p>

<pre class="yml">propel:
  blog_article:
    id:
    title:            varchar(50)
    created_at:
    _indexes:
      my_index:       [title(10), user_id]
    _uniques:
      my_other_index: [created_at]</pre>

<p>代替構文は複数のカラムで構築されたインデックスに対してのみ役立ちます。</p>

<a name="empty.columns" id="empty.columns"></a><h3>空のカラム</h3>

<p>値を持たないカラムに遭遇するとき、symfonyはいくつかの手品を行い、それ自身の値を追加します。空のカラムに追加された詳細内容に関してリスト8-31をご覧ください。</p>

<p>リスト8-31 - カラムの名前から推定されたカラムの詳細内容</p>

<pre><code>// idという名前で空のカラムは主キーと見なされる
id:         { type: integer, required: true, primaryKey: true, autoIncrement: true }

// XXX_idという名前で空のカラムは外部キーと見なされる
foobar_id:  { type: integer, foreignTable: db_foobar, foreignReference: id }

// created_at、updated at、created_onとupdated_onという名前を持つ空のカラムは
// 日付と見なされ自動的にtimestamp型をとる
created_at: { type: timestamp }
updated_at: { type: timestamp }
</code></pre>

<p>外部キーに対して、symfonyはカラムの名前の始めで同じ<code>phpName</code>を持つテーブルを探し、1つが見つかったら、このテーブルの名前を<code>foreignTable</code>としてとります。</p>

<a name="i18n.tables" id="i18n.tables"></a><h3>国際化テーブル</h3>

<p>symfonyは関連テーブル内で内容の国際化機能のサポートをします。このことは、内容の題目を国際化するとき、2つのテーブルに個別に保存されることを意味します: 1つは変わらないカラムでもう1つが国際化されたカラムです。</p>

<p><code>schema.yml</code>ファイルにおいて、テーブルを<code>footbar_i18n</code>と名づけたときにすべてが暗黙のうちに行われます。たとえば、国際化した内容のメカニズムが働くようにリスト8-32で示されるスキーマはカラムとテーブル属性を自動的に備えています。内部では、symfonyはあたかもリスト8-33のように書かれたものとして理解します。13章で国際化に関して詳しい説明が行われます。</p>

<p>リスト8-32 - 暗黙的な国際化のメカニズム</p>

<pre class="yml">propel:
  db_group:
    id:
    created_at:
&nbsp;
  db_group_i18n:
    name:        varchar(50)</pre>

<p>リスト8-33 - 明示的な国際化のメカニズム</p>

<pre class="yml">propel:
  db_group:
    _attributes: { isI18N: true, i18nTable: db_group_i18n }
    id:
    created_at:
&nbsp;
  db_group_i18n:
    id:       { type: integer, required: true, primaryKey: true,foreignTable: db_group, foreignReference: id, onDelete: cascade }
    culture:  { isCulture: true, type: varchar(7), required: true,primaryKey: true }
    name:     varchar(50)</pre>

<a name="behaviors.new.in.symfony.1.1" id="behaviors.new.in.symfony.1.1"></a><h3>ビヘイビアー (symfony 1.1の新しい機能)</h3>

<p>ビヘイビアー(behavior)は新しい機能をPropelのクラスに追加するプラグインによって提供されたモデルを修正するライブラリです。17章でビヘイビアーに関してより詳しく説明します。ビヘイビアーをそれぞれのテーブルに対して、パラメーターと一緒に、<code>_behaviors</code>キーの下に並べることでスキーマのなかでビヘイビアーを直接定義できます。リスト8-34は<code>BlogArticle</code>クラスを<code>paranoid</code>ビヘイビアーで拡張する例を示しています。</p>

<p>リスト8-34 - ビヘイビアーの宣言</p>

<pre class="yml">propel:
  blog_article:
    title:          varchar(50)
    _behaviors:
      paranoid:     { column: deleted_at }</pre>

<a name="beyond.the.schema.yml.the.schema.xml" id="beyond.the.schema.yml.the.schema.xml"></a><h3>schema.ymlを越えて: schema.xml</h3>

<p>実際のところ、<code>schema.yml</code>フォーマットはsymfonyの内部に存在します。<code>propel-command</code>を呼び出すとき、symfonyは実際にこのファイルを<code>generated-schema.xml</code>ファイルに翻訳します。このXMLファイルは実際にはモデル上のタスクを実行するためにPropelによって求められるタイプのファイルです。</p>

<p><code>schema.xml</code>ファイルはYAMLの同等のものとして同じ情報を含みます。たとえば、リスト8-35で示されるように、リスト8-3はXMLファイルに変換されます。</p>

<p>リスト8-35 - リスト8-3に対応する<code>schema.yml</code>のサンプル</p>

<pre class="xml"><span class="sc3"><span class="re1">&lt;?xml</span> <span class="re0">version</span>=<span class="st0">&quot;1.0&quot;</span> <span class="re0">encoding</span>=<span class="st0">&quot;UTF-8&quot;</span><span class="re2">?&gt;</span></span>
 <span class="sc3"><span class="re1">&lt;database</span> <span class="re0">name</span>=<span class="st0">&quot;propel&quot;</span> <span class="re0">defaultIdMethod</span>=<span class="st0">&quot;native&quot;</span> <span class="re0">noXsd</span>=<span class="st0">&quot;true&quot;</span> <span class="re0">package</span>=<span class="st0">&quot;lib.model&quot;</span><span class="re2">&gt;</span></span>
    <span class="sc3"><span class="re1">&lt;table</span> <span class="re0">name</span>=<span class="st0">&quot;blog_article&quot;</span> <span class="re0">phpName</span>=<span class="st0">&quot;Article&quot;</span><span class="re2">&gt;</span></span>
      <span class="sc3"><span class="re1">&lt;column</span> <span class="re0">name</span>=<span class="st0">&quot;id&quot;</span> <span class="re0">type</span>=<span class="st0">&quot;integer&quot;</span> <span class="re0">required</span>=<span class="st0">&quot;true&quot;</span> <span class="re0">primaryKey</span>=<span class="st0">&quot;true&quot;</span><span class="re0">autoIncrement</span>=<span class="st0">&quot;true&quot;</span> <span class="re2">/&gt;</span></span>
      <span class="sc3"><span class="re1">&lt;column</span> <span class="re0">name</span>=<span class="st0">&quot;title&quot;</span> <span class="re0">type</span>=<span class="st0">&quot;varchar&quot;</span> <span class="re0">size</span>=<span class="st0">&quot;255&quot;</span> <span class="re2">/&gt;</span></span>
      <span class="sc3"><span class="re1">&lt;column</span> <span class="re0">name</span>=<span class="st0">&quot;content&quot;</span> <span class="re0">type</span>=<span class="st0">&quot;longvarchar&quot;</span> <span class="re2">/&gt;</span></span>
      <span class="sc3"><span class="re1">&lt;column</span> <span class="re0">name</span>=<span class="st0">&quot;created_at&quot;</span> <span class="re0">type</span>=<span class="st0">&quot;timestamp&quot;</span> <span class="re2">/&gt;</span></span>
    <span class="sc3"><span class="re1">&lt;/table<span class="re2">&gt;</span></span></span>
    <span class="sc3"><span class="re1">&lt;table</span> <span class="re0">name</span>=<span class="st0">&quot;blog_comment&quot;</span> <span class="re0">phpName</span>=<span class="st0">&quot;Comment&quot;</span><span class="re2">&gt;</span></span>
      <span class="sc3"><span class="re1">&lt;column</span> <span class="re0">name</span>=<span class="st0">&quot;id&quot;</span> <span class="re0">type</span>=<span class="st0">&quot;integer&quot;</span> <span class="re0">required</span>=<span class="st0">&quot;true&quot;</span> <span class="re0">primaryKey</span>=<span class="st0">&quot;true&quot;</span><span class="re0">autoIncrement</span>=<span class="st0">&quot;true&quot;</span> <span class="re2">/&gt;</span></span>
      <span class="sc3"><span class="re1">&lt;column</span> <span class="re0">name</span>=<span class="st0">&quot;article_id&quot;</span> <span class="re0">type</span>=<span class="st0">&quot;integer&quot;</span> <span class="re2">/&gt;</span></span>
      <span class="sc3"><span class="re1">&lt;foreign-key</span> <span class="re0">foreignTable</span>=<span class="st0">&quot;blog_article&quot;</span><span class="re2">&gt;</span></span>
        <span class="sc3"><span class="re1">&lt;reference</span> <span class="re0">local</span>=<span class="st0">&quot;article_id&quot;</span> <span class="re0">foreign</span>=<span class="st0">&quot;id&quot;</span><span class="re2">/&gt;</span></span>
      <span class="sc3"><span class="re1">&lt;/foreign-key<span class="re2">&gt;</span></span></span>
      <span class="sc3"><span class="re1">&lt;column</span> <span class="re0">name</span>=<span class="st0">&quot;author&quot;</span> <span class="re0">type</span>=<span class="st0">&quot;varchar&quot;</span> <span class="re0">size</span>=<span class="st0">&quot;255&quot;</span> <span class="re2">/&gt;</span></span>
      <span class="sc3"><span class="re1">&lt;column</span> <span class="re0">name</span>=<span class="st0">&quot;content&quot;</span> <span class="re0">type</span>=<span class="st0">&quot;longvarchar&quot;</span> <span class="re2">/&gt;</span></span>
      <span class="sc3"><span class="re1">&lt;column</span> <span class="re0">name</span>=<span class="st0">&quot;created_at&quot;</span> <span class="re0">type</span>=<span class="st0">&quot;timestamp&quot;</span> <span class="re2">/&gt;</span></span>
    <span class="sc3"><span class="re1">&lt;/table<span class="re2">&gt;</span></span></span>
 <span class="sc3"><span class="re1">&lt;/database<span class="re2">&gt;</span></span></span></pre>

<p><code>schema.xml</code>フォーマットの記述方法はPropelプロジェクトWebサイト(<a href="http://propel.phpdb.org/docs/user_guide/chapters/appendices/AppendixB-SchemaReference.html">http://propel.phpdb.org/docs/user_guide/chapters/appendices/AppendixB-SchemaReference.html</a>)ドキュメントと"Getting Started"のセクションで見ることができます。</p>

<p>YAMLフォーマットはスキーマの読み書きをシンプルに保つために設計されましたが、 トレードオフはもっとも複雑なスキーマを<code>schema.yml</code>ファイルで記述できないことです。一方で、XMLフォーマットは、どんなに複雑なものであれ、データベースのベンダー固有の設定、テーブル、継承などを含めて、完全なスキーマ構文を記述できます。</p>

<p>実際にはsymfonyはXMLフォーマットで書かれたスキーマを理解します。あなたのスキーマがYAMLの構文で記述するには複雑すぎる場合、既存のXMLスキーマを持つ場合、もしくはすでにPropelのXMLフォーマットに慣れ親しんでいる場合、symfonyのYAML構文に切り替える必要はありません。<code>schema.yml</code>をプロジェクトの<code>config/</code>ディレクトリに設置し、モデルをビルドします。簡単でしょ。</p>

<blockquote class="sidebar"><p class="title">
  <strong>symfonyにおけるPropel</strong></p>
  
  <p>この章で説明されたすべての内容はsymfony固有のものではなく、むしろPropelのものです。Propelはsymfonyに対して優先されるオブジェクト/リレーショナル抽象化レイヤーですが、代わりのものを選ぶことができます。しかしながら、つぎの理由から、symfonyはPropelでよりシームレスに動作します:</p>
  
  <p>すべてのオブジェクトデータモデルクラスと<code>Criteria</code>クラスはオートロードクラスです。それらを使うと同時に、symfonyは正しいファイルをインクルードし、ファイルをインクルードするステートメントを手動で追加する必要はありません。symfonyにおいて、Propelを起動したり、初期化する必要もありません。オブジェクトがPropelを利用するとき、ライブラリは自分自身で初期化を行います。symfonyヘルパーはハイレベルなタスク(たとえばパジネーションもしくはフィルタリング)を実現するためにPropelオブジェクトをパラメーターとして使います。Propelオブジェクトはアプリケーションに対してラピッドプロトタイピングとバックエンドの生成を可能にします(14章で詳細な説明を提供します)。スキーマは<code>schema.yml</code>ファイルを通して速く書けます。</p>
  
  <p>Propelがデータベースに対して独立していることと同様に、symfonyもPropelに対して独立しています。</p>
</blockquote>

<a name="dont.create.the.model.twice" id="dont.create.the.model.twice"></a><h2>同じモデルを2回作らない</h2>

<p>ORMを使う場合のトレードオフはデータ構造を2回定義しなければならないことです: 1回目はデータベースに対して、2回目はオブジェクトモデルに対してです。幸いにも、symfonyは一方に基づいてもう一方を生成するコマンドラインツールを提供するので、重複作業を回避できます。</p>

<a name="building.a.sql.database.structure.based.on.an.existing.schema" id="building.a.sql.database.structure.based.on.an.existing.schema"></a><h3>既存のスキーマに基づいてSQLのデータベース構造をビルドする</h3>

<p><code>schema.yml</code>ファイルを書くことでアプリケーションを始める場合、symfonyはYAMLデータモデルから直接テーブルを作成するSQLクエリを生成できます。クエリを使うために、プロジェクトのrootに移動し、つぎのコマンドを入力します:</p>

<pre><code>&gt; php symfony propel:build-sql
</code></pre>

<p><code>lib.model.schema.sql</code>ファイルは<code>myproject/data/sql/</code>に作られます。生成されたSQLコードが<code>propel.ini</code>ファイルの<code>phptype</code>パラメーターで定義されたデータベースシステムに対して最適化されることを覚えておいてください。</p>

<p>テーブルを直接ビルドするために<code>schema.yml</code>ファイルを利用できます。たとえば、MySQLでは、つぎのコマンドを入力します:</p>

<pre><code>&gt; mysqladmin -u root -p create blog
&gt; mysql -u root -p blog &lt; data/sql/lib.model.schema.sql
</code></pre>

<p>生成されたSQLもほかの環境のデータベースのリビルド、もしくはほかのDBMSに変更するために役立ちます。接続設定が<code>propel.ini</code>で適切に定義される場合、これを自動的に行うsymfonyの<code>propel:insert-sql</code>を利用することもできます。</p>

<blockquote class="tip"><p>
  コマンドラインはテキストファイルに基づいたデータをデータベースに投入するタスクも提供します。<code>propel:data-load</code>タスクとYAMLフィクスチャファイルの詳細な情報は16章をご覧ください。</p>
</blockquote>

<a name="generating.a.yaml.data.model.from.an.existing.database" id="generating.a.yaml.data.model.from.an.existing.database"></a><h3>既存のデータベースからYAMLのデータモデルを生成する</h3>

<p>イントロスペクション(introspection データベースが影響を与えるテーブルの構造を決定するデータベースの機能)のおかげで、symfonyは既存のデータベースから<code>schema.yml</code>ファイルを生成するためにCreoleデータベースアクセスレイヤーを使うことができます。これはリバースエンジニアリングを行うとき、もしくはオブジェクトモデルよりもデータベースにとり組みたい場合に役立ちます。</p>

<p>これを行うために、プロジェクトの<code>propel.ini</code>ファイルが正しいデータベースを指し示しすべての接続設定を含んでいることを確認する必要があります。それから<code>propel:build-schema</code>コマンドを呼び出します:</p>

<pre><code>&gt; php symfony propel:build-schema
</code></pre>

<p>データベース構造からビルドされた新品の<code>schema.yml</code>ファイルは<code>config/</code>ディレクトリに生成されます。このスキーマに基づいてモデルをビルドできます。</p>

<p>スキーマ生成のコマンドはとても強力でデータベースに依存する多くの情報をスキーマに追加できます。YAMLフォーマットはこの種のベンダーの情報を扱うことができないので、この情報を利用するにはXMLフォーマットを生成する必要があります。<code>xml</code>の引数を<code>build-schema</code>タスクに追加することでこれを簡単に行うことができます:</p>

<pre><code>&gt; php symfony propel:build-schema --xml
</code></pre>

<p><code>schema.yml</code>ファイルを生成する代わりに、これは、Propelと十分に互換性を持ち、すべてのベンダーの情報を含む<code>schema.xml</code>ファイルを作ります。しかし、生成されたXMLスキーマは読むにはとても冗長で難しいことを念頭に置いてください。</p>

<blockquote class="sidebar"><p class="title">
  propel.iniの設定</p>
  
  <p><code>propel:build-sql</code>と<code>propel:build-schema</code>タスクは<code>databases.yml</code>ファイルで定義された接続設定を使いません。むしろ、<code>propel.ini</code>という名前の別のファイルの接続設定を使います。<code>propel.ini</code>はプロジェクトの<code>config/</code>ディレクトリに保存されています:</p>

<pre><code> propel.database.createUrl = mysql://login:passwd@localhost
 propel.database.url       = mysql://login:passwd@localhost/blog
</code></pre>
  
  <p>このファイルは生成されたモデルクラスをsymfonyと互換性のあるものにするPropelジェネレーターを設定するために使われるほかの設定を含みます。ごく一部を除いて、多くの設定は内部に関するもので、ユーザーにとっては面白くないものです:</p>

<pre><code> // Baseクラスはsymfonyでオートロードされる
 // 代わりにinclude_onceステートメントを使うためにこれをtrueに設定する
 // (パフォーマンスに対してわずかながら負な影響がある)
 propel.builder.addIncludes = false

 // 生成されたクラスはデフォルトでコメントされない
 // コメントをBaseクラスに追加するためにこれをtrueに設定する
 // (パフォーマンスに小さな負の影響がある)
 propel.builder.addComments = false

 // ビヘイビアーはデフォルトで扱われない
 // これらを扱うことができるようにするにはつぎの項目をtrueに設定する
 propel.builder.AddBehaviors = false
</code></pre>
  
  <p><code>propel.ini</code>設定ファイルの修正を行った後に、変更が反映されるようにモデルをリビルドすることを忘れないでください。</p>
</blockquote>

<a name="summary" id="summary"></a><h2>まとめ</h2>

<p>symfonyはPropelをオブジェクトリレーショナルマッピング(ORM - Object-Relational Mapping)として、Creoleをデータベース抽象化レイヤーとして利用します。これはオブジェクトモデルクラスを生成するまえに、最初にYAMLフォーマットでデータベースのリレーショナルスキーマを記述しなければならないことを意味します。それから、実行時において、オブジェクトのメソッドとレコードもしくはレコードのセットについての情報をとり出すためにピアクラスを使います。接続設定は複数の接続をサポートする<code>databases.yml</code>ファイルで定義されます。そして、コマンドラインは重複して構造を定義しないようにする特別なタスクを含みます。</p>

<p>モデルレイヤーはsymfonyフレームワークのなかでもっとも複雑です。複雑である理由の1つはデータ操作が込み入った問題であるからです。関連するセキュリティ問題はWebサイトにとって重大で、無視できません。ほかの理由はsymfonyが中規模から大規模のアプリケーションにもっとも適しているからです。このようなアプリケーションにおいて、symfonyのモデルによって提供された自動化は本当に時間を節約するので、内部構造を学ぶ価値はあります。</p>

<p>ですので、モデルオブジェクトとメソッドを十分に理解するにはこれらをテストすることに時間を費やすことを躊躇しないでください。アプリケーションの堅牢性とスケーラビリティが大きな報酬として得られます。</p>
</div>
<div class="navigation">
<hr/>
<table width="100%">
<tr>
<td width="40%" align="left"><a href="07-Inside-the-View-Layer.html">前の章</a></td>
<td width="20%" align="center"><a href="index.html">ホーム</a></td>
<td width="40%" align="right"><a href="09-Links-and-the-Routing-System.html">次の章</a></td>
</tr>
</table>

</div>
</body>

</html>
